#ifndef __SELECTSERVER_HPP__
#define __SELECTSERVER_HPP__

#include <iostream>
#include <memory>
#include "Socket.hpp"
using namespace SocketModule;

class SelectServer
{
    const static int size = sizeof(fd_set) * sizeof(void *);
    const static int defaultfd = -1;

public:
    SelectServer(uint16_t port)
        : _ptr(std::make_unique<TCPSocket>(port)),
          _isrunning(false)
    {
        _ptr->BuildTCPSocketfd(port);
        // 初始化fd数组
        for (int i = 0; i < size; i++)
        {
            _fd_array[i] = defaultfd;
        }
        // _fd_array[0]是监听套接字，需要做额外处理
        _fd_array[0] = this->_ptr->getFD();
    }

    void Start()
    {
        if (_isrunning)
            return;
        if (!_isrunning)
            _isrunning = true;
        while (true)
        {
            PrintFD();
            // 还像之前写http时直接调用accept进行监听是不行的，它是一个阻塞调用，不符合多路转接思想
            // 直接使用accept，如果没有新连接到来，程序就会一直阻塞
            // 所以要让OS关心ptr的fd是否有新连接到来
            fd_set rfds;
            FD_ZERO(&rfds);

            // 初始化过程中获取maxfd即可
            int maxfd = defaultfd;
            // 把有效的套接字设置到读fd集合中
            for (int i = 0; i < size; i++)
            {
                if (_fd_array[i] != defaultfd)
                {
                    FD_SET(_fd_array[i], &rfds); // 还没有设置在内核中
                    if (_fd_array[i] > maxfd)
                        maxfd = _fd_array[i];
                }
            }

            // 设置非阻塞调用
            // struct timeval timeout;
            // timeout.tv_sec = timeout.tv_usec = 0;

            // 阻塞读容易看出效果
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            // 处理select的返回值
            /**
             * 大于0，代表有几个fd就绪，但是并不是所有fd都就绪了，下一次又把读fd集合重置了，
             * 无法区分哪些fd已经建立连接了，所以需要一个数组来管理fd
             * 等于0，超时
             * 小于0，参数错误
             */
            if (n > 0)
            {
                LOG(INFO) << "有事件就绪了";
                Dispatcher(rfds);
                continue;
            }
            else if (n == 0)
            {
                LOG(INFO) << "time out";
                continue;
            }
            else
            {
                LOG(ERROR) << "参数错误";
                continue;
            }
        }
        _isrunning = false;
    }

    void Dispatcher(fd_set &rfds)
    {
        // 把已经就绪的fd在fd数组中删去
        for (int i = 0; i < size; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            if (FD_ISSET(_fd_array[i], &rfds))
            {
                // 读事件就绪，建立新连接，获取新的sockfd
                // 监听是否有客户端想要连接
                // 还是IO = 等 + 拷贝的思想
                // listensockfd只负责等，普通fd负责拷贝
                if (_fd_array[i] == _ptr->getFD())
                {
                    Accepter();
                }
                // 监听客户端发送数据
                else
                {
                    Recver(_fd_array[i], i);
                }
            }
        }
    }
    void Accepter()
    {
        InetAddr *client;
        int sockfd = _ptr->AcceptSocket(client);

        if (sockfd >= 0)
        {
            // 获取新连接成功了，但是不能直接read/recv，我们不知道sockfd是否有事件就绪
            // select最清楚sockfd未来是否会有事件就绪，将它放入fd数组
            LOG(INFO) << "get a new link,sockfd is " << sockfd << ",client :" << client->StringAddrIP();
            int index = 0;
            for (; index < size; index++)
            {
                if (_fd_array[index] == defaultfd)
                    break;
            }

            if (index == size)
            {
                LOG(FATAL) << "select server full";
                close(sockfd);
            }
            else
            {
                _fd_array[index] = sockfd;
            }
        }
    }
    void Recver(int fd, int index)
    {
        char buffer[1024];
        ssize_t n = recv(fd, buffer, sizeof buffer, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say@ " << buffer << std::endl;
        }
        else if (n == 0)
        {
            // 对方关闭了fd
            LOG(INFO) << "client quit...";

            _fd_array[index] = defaultfd;
            close(fd);
        }
        else
        {
            LOG(ERROR) << "recv error";
            // close(fd);
            _fd_array[index] = defaultfd;
            close(fd);
        }
    }
    void PrintFD()
    {
        std::cout << "_fd_array[]: ";
        for (int i = 0; i < size; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            std::cout << _fd_array[i] << " ";
        }
        std::cout << "\r\n";
    }
    void Stop()
    {
        if (!_isrunning)
            return;
        _isrunning = false;
    }
    ~SelectServer()
    {
    }

private:
    std::unique_ptr<Socket> _ptr;
    bool _isrunning;
    int _fd_array[size];
};

#endif